package com.dungeon.game;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

import android.graphics.Color;

import com.dungeon.game.Constants.Character_action_code;
import com.dungeon.game.Constants.Class_code;
import com.dungeon.game.Constants.Position_code;
import com.dungeon.game.Constants.Potion_type;
import com.dungeon.game.Constants.Stance_code;
import com.dungeon.game.Constants.Status_effect_code;
import com.dungeon.game.Enemies.Mob;

public class Party implements Serializable{

	public class Character implements Serializable {

		int number;
		Position_code pos;
		ArrayList<Position_code> pos_record;
		int level, aggro, strength, skill, valor, wisdom, daring, cunning, power, subtlety, learning, constitution, reflexes, stamina, speed, damage, accuracy, melee_dice, melee_sides;
		int rapidity, missile_dice, missile_sides, penetration, aim, armor, evasion, max_health, health, energy, max_energy, xp;
		String name;
		Class_code class_code;
		String image, flash_image;
		String melee_hit_sound, melee_miss_sound, missile_hit_sound, missile_miss_sound;
		double energy_gain_period;
		boolean energy_for_healing,energy_for_gold,energy_for_evil_kill,energy_for_shot,energy_for_hit,energy_for_getting_hit,concentration,auto_retrench;
		boolean melee_attack, poison_melee, fire_melee, dragon_slaying_melee, undead_slaying_melee, missile_attack, magebane_missile, poison_missile, fire_missile;
		ArrayList<Character_action> actions;
		Character_action action_in_progress;
		double last_energy_gain, dumb_until, stance_wears_off, die_at, action_fires, webbed_until;
		boolean alive, about_to_die, fled;
		Stance_code current_stance;
		Position_code slide_from, slide_to;
		double slide_start, slide_finish;
		HashMap<Status_effect_code,Effect_parameters> status_effects;
		Mob counter_against, grappled_by;
		double flash_starts, number_starts;
		boolean flash_long;
		int flash_number;
		int number_color;
		ArrayList<Potion_type> potions_used;
		boolean fire_resistance, poison_resistance, cold_resistance, sleep_resistance, draining_resistance, lightning_resistance;
		double next_energy_drain;
		boolean drowning;
		double drowning_from;
		int cunning_for_hide, cunning_for_treasure, power_for_bolt, power_for_mass, subtlety_for_fear, wisdom_for_healing;
		int n_balm_remaining;
        double next_balm_at;
        boolean physical_resistance, adversity;
        boolean lose_health;

		public Character(Charsheet charsheet, int number_within_party, Position_code _pos){
			pos = _pos;
			pos_record = new ArrayList<Position_code>();
			number = number_within_party;
			// start looking at charsheet
			level = charsheet.level;
			name = charsheet.name;
			class_code = charsheet.class_code;
			image = charsheet.image;
			energy_gain_period = 0;
			energy_for_healing = false;
			energy_for_gold = false;
			energy_for_evil_kill = false;
			energy_for_shot = false;
			energy_for_hit = false;
			energy_for_getting_hit = false;
			concentration = false;
			auto_retrench = false;
			fire_resistance = false;
			poison_resistance = false;
			cold_resistance = false;
			sleep_resistance = false;
			draining_resistance = false;
			lightning_resistance = false;
			physical_resistance = false;
			adversity = false;
			lose_health = false;
			aggro = charsheet.get_aggro();
			strength = 0;
			skill = 0;
			valor = 0;
			wisdom = 0;
			daring = 0;
			cunning = 0;
			power = 0;
			subtlety = 0;
			cunning_for_hide=0;
			cunning_for_treasure=0;
			power_for_bolt=0;
			power_for_mass=0;
			subtlety_for_fear=0;
			wisdom_for_healing=0;
			if (class_code==Class_code.C_WARRIOR){
				strength = charsheet.get_strength();
				skill = charsheet.get_skill();
				energy_gain_period = 2;
			} else if (class_code==Class_code.C_PRIEST){
				valor = charsheet.get_valor();
				wisdom = charsheet.get_wisdom();
				energy_for_evil_kill = true;
			} else if (class_code==Class_code.C_ROGUE){
				daring = charsheet.get_daring();
				cunning = charsheet.get_cunning();
				energy_for_gold = true;
			} else if (class_code==Class_code.C_MAGE){
				power = charsheet.get_power();
				subtlety = charsheet.get_subtlety();
				energy_gain_period = 6;
			}
			learning = charsheet.intelligence;
			constitution = charsheet.get_constitution();
			reflexes = charsheet.get_reflexes();
			stamina = charsheet.get_stamina();
			melee_attack = true;
			speed = charsheet.get_speed();
			damage = charsheet.get_damage();
			accuracy = charsheet.get_accuracy();
			if (charsheet.melee_weapon != null) {
				melee_dice = charsheet.melee_weapon.base_damage_dice();
				melee_sides = charsheet.melee_weapon.base_damage_sides();
				melee_hit_sound = charsheet.melee_weapon.hit_sound();
				melee_miss_sound = charsheet.melee_weapon.miss_sound();
				if (charsheet.melee_weapon.lose_health()){
					lose_health = true;} // Life draining sword - only way you can get this malus
			} else {
				melee_dice = 1;
				melee_sides = 4;
				melee_hit_sound = "monhit";
				melee_miss_sound = "monmiss";
			}
			poison_melee = charsheet.poison_melee();
			fire_melee = charsheet.fire_melee();
			dragon_slaying_melee = false;
			undead_slaying_melee = false;
			magebane_missile = false;
			missile_attack = false;
			if (charsheet.missile_weapon!=null){
				missile_attack = true;
				rapidity = charsheet.get_rapidity();
				missile_dice = charsheet.missile_weapon.base_damage_dice();
				missile_sides = charsheet.missile_weapon.base_damage_sides();
				penetration = charsheet.get_penetration();
				aim = charsheet.get_aim();
				missile_hit_sound = charsheet.missile_weapon.hit_sound();
				missile_miss_sound = charsheet.missile_weapon.miss_sound();
				poison_missile = charsheet.poison_missile();
				fire_missile = charsheet.fire_missile();
			}
			armor = charsheet.get_armor();
			evasion = charsheet.get_evasion();
			actions = new ArrayList<Character_action>();
			for (Character_action_code action_code : charsheet.get_action_codes()){
				if (action_code==Character_action_code.CA_CAROUSE || action_code==Character_action_code.CA_INSPIRING || action_code==Character_action_code.CA_HAGGLER || action_code==Character_action_code.CA_SHOOT || action_code==Character_action_code.CA_SLING || action_code==Character_action_code.CA_ZEN_ARCHER || action_code==Character_action_code.CA_APOTHECARY || action_code==Character_action_code.CA_PRECOCIOUS || action_code==Character_action_code.CA_PACKRAT)
					continue;
				Character_action a = Gamestate.ca.make_action(action_code);
				if (a!=null)
					add_action(a,action_code);
				if (action_code==Character_action_code.CA_DRAGON_SLAYER){
					dragon_slaying_melee = true;
				}
				if (action_code==Character_action_code.CA_ARMOR){
					armor = armor + 1;
				}
				if (action_code==Character_action_code.CA_TOUGH){
					armor = armor + 2;
				}
				if (action_code==Character_action_code.CA_ADVERSITY){
					adversity = true;
				}
				if (action_code==Character_action_code.CA_PHYSICAL_RESISTANCE){
					physical_resistance = true;
				}
				if (action_code==Character_action_code.CA_AGGRO){
					aggro = aggro + 1;}
				if (action_code==Character_action_code.CA_ROSARY){
					energy_gain_period = 2;
					energy_for_evil_kill = false;
				}
				if (action_code==Character_action_code.CA_RECUPERATE){
					if (energy_gain_period==0)
						energy_gain_period = 4;
					else if (energy_gain_period>2)
						energy_gain_period = 1.5 + 0.4*(energy_gain_period-2.0);
					else
						energy_gain_period = energy_gain_period * 0.75;
				}
				if (action_code==Character_action_code.CA_HEALER){
					energy_for_healing = true;
					energy_for_evil_kill = false;
				}
				if (action_code==Character_action_code.CA_ARCHER){
					energy_for_gold = false;
					energy_for_shot = true;
				}
				if (action_code==Character_action_code.CA_ASSASSIN){
					energy_for_gold = false;
					energy_for_hit = true;
				}
				if (action_code==Character_action_code.CA_PENETRATION){
					penetration = penetration + 1;
				}
				if (action_code==Character_action_code.CA_INCONSPICUOUS){
					evasion = evasion + 1;
					aggro = aggro - 1;
				}
				if (action_code==Character_action_code.CA_INSOLENT){
					aggro = aggro + 1;
				}
				if (action_code==Character_action_code.CA_EVASIVE){
					evasion = evasion + 2;
				}
				if (action_code==Character_action_code.CA_HUNTER){
					aim = aim + 2;
				}
				if (action_code==Character_action_code.CA_WAND_LORE){
					aim = aim + 1;
					rapidity = rapidity + 1;
				}
				if (action_code==Character_action_code.CA_WAND_LORE_2){
					aim = aim + 2;
					rapidity = rapidity + 2;
				}
				if (action_code==Character_action_code.CA_FAST){
					speed = speed + 1;
					rapidity = rapidity + 1;
				}
				if (action_code==Character_action_code.CA_SNEAKY){
					evasion = evasion + 2;
				}
				if (action_code==Character_action_code.CA_LETHAL){
					accuracy = accuracy + 2;
					damage = damage + 2;
				}
				if (action_code==Character_action_code.CA_INOFFENSIVE){
					aggro = aggro - 1;
				}
				if (action_code==Character_action_code.CA_BRUTAL){
					damage = damage + 1;
				}
				if (action_code==Character_action_code.CA_DEFT){
					accuracy = accuracy + 1;
					aim = aim + 1;
				}
				if (action_code==Character_action_code.CA_AGILE){
					evasion = evasion + 1;
				}
				if (action_code==Character_action_code.CA_CONCENTRATION){
					concentration = true;
				}
				if (action_code==Character_action_code.CA_UNDEAD_SLAYER){
					undead_slaying_melee = true;
				}
				if (action_code==Character_action_code.CA_MAGEBANE){
					magebane_missile = true;
				}
				if (action_code==Character_action_code.CA_RAGE){
					energy_for_getting_hit = true;
				}
				if (action_code==Character_action_code.CA_VETERAN){
					auto_retrench = true;
				}
				if (action_code==Character_action_code.CA_FIRE_RESISTANCE){
					lightning_resistance = true;
				}
				if (action_code==Character_action_code.CA_FIRE_RESISTANCE){
					fire_resistance = true;
				}
				if (action_code==Character_action_code.CA_COLD_RESISTANCE){
					cold_resistance = true;
				}
				if (action_code==Character_action_code.CA_RESIST_DRAINING){
					draining_resistance = true;
				}
				if (action_code==Character_action_code.CA_WATCHFUL){
					sleep_resistance = true;
				}
				if (action_code==Character_action_code.CA_POISON_RESISTANCE){
					poison_resistance = true;
				}
				if (action_code==Character_action_code.CA_KNIGHT || action_code==Character_action_code.CA_TEMPLAR){
					armor = armor + 1;
					accuracy = accuracy + 1;
				}
				if (action_code==Character_action_code.CA_BARBARIAN || action_code==Character_action_code.CA_SWASHBUCKLER){
					damage = damage + 1;
					speed = speed + 1;
				}
				if (action_code==Character_action_code.CA_RANGER){
					aim = aim + 1;
					evasion = evasion + 1;
				}
				if (action_code==Character_action_code.CA_MARKSMAN){
					aim = aim + 1;
					penetration = penetration + 1;
				}
				if (action_code==Character_action_code.CA_PALADIN){
					aggro = aggro + 1;
					damage = damage + 1;
				}
				if (action_code==Character_action_code.CA_BURGLAR){
					cunning_for_hide = cunning_for_hide + 1;
				}
				if (action_code==Character_action_code.CA_THIEF){
					cunning_for_treasure = cunning_for_treasure + 1;
				}
				if (action_code==Character_action_code.CA_WARLOCK){
					power_for_bolt = power_for_bolt + 1;
				}
				if (action_code==Character_action_code.CA_THAUMATURGE){
					power_for_mass = power_for_mass + 1;
				}
				if (action_code==Character_action_code.CA_ENCHANTER){
					subtlety_for_fear = subtlety_for_fear + 1;
				}
				if (action_code==Character_action_code.CA_MEDIC){
					wisdom_for_healing = wisdom_for_healing + 1;
				}
			}
			for (Potion_type j : charsheet.potions){
				add_potion_action(j);
			}
			// finished looking at charsheet;
			alive = true;
			about_to_die = false;
			fled = false;
			if (constitution==1){
				max_health = (int)Math.ceil((constitution + 2.4) * (level + 3));
			} else {
				max_health = (int)Math.ceil((6+(constitution-3)*1.3) * (level + 3));
			}
			if (Campaignstate.difficulty_level==2) max_health = (int)Math.ceil(0.87*max_health);
			if (Campaignstate.difficulty_level==3) max_health = (int)Math.ceil(0.75*max_health);
			if (charsheet.get_action_codes().contains(Character_action_code.CA_ROBUST)) max_health = max_health+5;
			health = max_health;
			max_energy = 15 + 5 * level;
			if (Campaignstate.difficulty_level==2) max_energy = (int)Math.ceil(0.87*max_energy);
			if (Campaignstate.difficulty_level==3) max_energy = (int)Math.ceil(0.75*max_energy);
			xp = 0;
			energy = max_energy;
			action_in_progress = null;
			last_energy_gain = 0;
			dumb_until = 0;
			current_stance = null;
			stance_wears_off = 0;
			counter_against = null;
			status_effects = new HashMap<Status_effect_code,Effect_parameters>();
			flash_image = null;
			flash_long = false;
			flash_starts = -1;
			flash_number = 0;
			number_starts = -1;
			number_color = -1;
			grappled_by = null;
			slide_from = null;
			slide_to = null;
			slide_start = 0;
			slide_finish = 0;
			die_at = -1;
			action_fires = -1;
			potions_used = new ArrayList<Potion_type>();
			next_energy_drain = 0;
			drowning = false;
			drowning_from = 0;
			n_balm_remaining = 0;
	        next_balm_at = 0.0;
	        webbed_until = 0.0;
		}

		boolean front_row() {
			if (pos==null) return false;
			return pos.is_front_row();
		}

		void add_action(Character_action action, Character_action_code action_code) {
			action.c = this;
			action.action_code = action_code;
			actions.add(action);
		}

	    void add_potion_action(Potion_type p){
	    	Character_action_code ac = p.get_character_action_code();
	    	boolean found = false;
	    	for (Character_action a : actions){
	    		if (a.action_code==ac){
	    			found = true;
	    			a.limited_uses = a.limited_uses + 1;
	    		}
	    	}
	    	if (!found){
	    		Character_action action = Gamestate.ca.make_action(ac);
	    		action.c = this;
	    		action.action_code = ac;
	    		action.potion_code = p;
	    		action.limited_uses = 1;
	    		actions.add(action);
	    	}
	    }

	    double melee_dps_from(double mob_melee_period, int mob_accuracy, int mob_dice, int mob_sides){
	    	int target = 2 * (5 + mob_accuracy - current_evasion());
	    	if (target<1) target=1;
	    	if (target>19) target=19;
	    	double mean_damage = 0;
			for (int i = 0; i < 500; i++) {
				int x = 0;
	            for (int j=0; j<mob_dice; j++)
	                x = x + Gamestate.w.randint(1,mob_sides);
	            mean_damage = mean_damage + Math.max(0, (x - current_armor())) / 500.0;
			}
			return mean_damage * (target / 20.0) * Constants.TIME_SCALAR / mob_melee_period;
	    }

	    double melee_dps_vs(int armor, int evasion) {
			int target = 2 * (5 + current_accuracy() - evasion);
			if (target < 1)
				target = 1;
			if (target > 19)
				target = 19;
			double mean_damage = 0;
			for (int i = 0; i < 500; i++) {
				mean_damage = mean_damage + Math.max(0, (melee_damage_roll(null) - armor)) / 500.0;
			}
			return mean_damage * (target / 20.0) * Constants.TIME_SCALAR / current_melee_period();
		}

	    double missile_dps_vs(int armor, int evasion){
	            int target = 2 * (5 + current_aim() - evasion);
	            if (target<1)
	                target = 1;
	            if (target>19)
	                target = 19;
	            double mean_damage = 0;
	            for (int i=0; i<500; i++){
	                mean_damage = mean_damage + Math.max(0,(missile_damage_roll(null) - armor))/500.0;
	            }
	            return mean_damage * (target / 20.0) * Constants.TIME_SCALAR / current_missile_period();
	    }

	    boolean melee_hit_roll(Mob target){
	            int t = 2 * (5 + current_accuracy() - target.current_evasion());
	            if (t<1)
	                t = 1;
	            if (t>19)
	                t = 19;
	            return (Gamestate.w.randint(1,20)<=t);
	    }

	    boolean missile_hit_roll(Mob target){
                int evasion;
	            if (magebane_missile){
	                evasion = target.evasion;
	            } else {
	                evasion = target.current_evasion();
	            }
	            int t = 2 * (5 + current_aim() - evasion);
	            if (t<1)
	                t = 1;
	            if (t>19)
	                t = 19;
	            return (Gamestate.w.randint(1,20)<=t);
	    }

	    int melee_damage_roll(Mob m){
	            int x = 0;
	            for (int i=0; i<melee_dice; i++)
	                x = x + Gamestate.w.randint(1,melee_sides);
	            int base = (int)(Math.floor(x * (10.5 + current_damage()) / 10));
	            if (m!=null){
	                if (poison_melee && m.poison_resistant)
	                    base = base / 3;
	                if (fire_melee && m.fire_resistant)
	                    base = base / 3;
	                if (dragon_slaying_melee && m.dragon)
	                    base = (base * 3) / 2;
	                if (undead_slaying_melee && m.undead)
	                    base = base * 2;
	            }
	            if (adversity){
	            	base = (int)Math.ceil(base * Math.min(1.4, 1.8 - ((double)health)/max_health));
	            }
	            return base;
	    }

	    int missile_damage_roll(Mob m){
	            int x = 0;
	            for (int i=0; i<missile_dice; i++)
	                x = x + Gamestate.w.randint(1,missile_sides);
	            int base = (int)(Math.floor(x * (10.5 + current_penetration()) / 10));
	            if (m!=null){
	                if (poison_missile && m.poison_resistant)
	                    base = base / 3;
	                if (fire_missile && m.fire_resistant)
	                    base = base / 3;
	            }
	            return base;
	    }

	    void take_damage(int damage){
	            if (about_to_die || !alive || has_status_effect(Status_effect_code.EF_PETRIFY))
	                return;
				if (damage<0)
			    	return;
				if (Gamestate.w.tutorial_mode && damage>=health){
					damage = health-1;
				}
			    for (Character c : Gamestate.p.chars){
	                if (c.alive && (c.current_stance==Stance_code.ST_INVULNERABLE || c.has_status_effect(Status_effect_code.EF_INVULNERABLE))){
	                    return;
	                }
	            }
	            if (has_status_effect(Status_effect_code.EF_CURSE))
	                damage = damage * 2;
	            if (damage<10 && damage>1 && energy>=4){
	                for (Character_action this_action : actions){
	                    if (this_action.action_code==Character_action_code.CA_JUST_A_SCRATCH && (this_action.last_used==null || this_action.last_used<0 || this_action.last_used<=Gamestate.w.clock-5)){
	                        if (Gamestate.w.randint(1,100)<=this_action.success_chance()){
	                            this_action.last_used = Gamestate.w.clock;
	                            energy = energy - 4;
	                            Gamestate.add_message("Just a scratch!",image,Color.WHITE);
	                            return;
	                        }
	                    }
	                }
	            }
	            if (damage>=10 && damage<20 && energy>=12){
	                for (Character_action this_action : actions){
	                    if (this_action.action_code==Character_action_code.CA_LUCKY_ESCAPE && (this_action.last_used==null || this_action.last_used<0 || this_action.last_used<=Gamestate.w.clock-10)){
	                        if (Gamestate.w.randint(1,100)<=this_action.success_chance()){
	                            this_action.last_used = Gamestate.w.clock;
	                            energy = energy - 12;
	                            Gamestate.add_sound("whoosh");
	                            Gamestate.add_message("Lucky Escape!",image,Color.WHITE);
	                            return;
	                        }
	                    }
	                }
	            }
	            if (damage>=20 && damage<=70 && energy>=24){
	                for (Character_action this_action : actions){
	                    if ((this_action.action_code==Character_action_code.CA_SAVING_THROW || this_action.action_code==Character_action_code.CA_SALVATION) && (this_action.last_used==null || this_action.last_used<0 || this_action.last_used<=Gamestate.w.clock-10)){
	                        if (Gamestate.w.randint(1,100)<=this_action.success_chance()){
	                            this_action.last_used = Gamestate.w.clock;
	                            energy = energy - 24;
	                            if (this_action.action_code==Character_action_code.CA_SAVING_THROW){
		                            Gamestate.add_sound("whoosh");
		                            Gamestate.add_message("Makes a saving throw!",image,Color.WHITE);
	                            } else {
		                            Gamestate.add_sound("light");
	                            	Gamestate.add_message("Saved!",image,Color.WHITE);
	                            }
	                            return;
	                        }
	                    }
	                }
	            }
	            if (damage > 0){
	                flash_number = damage;
	                number_color = Color.RED;
	                number_starts = Gamestate.w.clock;
	    	        Gamestate.w.party_damage_times.add(Gamestate.w.clock);
	    	        Gamestate.w.party_damage_amounts.add((Integer)Math.min(damage,health));
	            }
	            health = health - damage;
	            if (health<=0){
	                about_to_die = true;
	                die_at = Gamestate.w.clock + 1.7;
	                return;
	            }
	            if (damage>1 && action_in_progress!=null && action_in_progress.disruptable()){
	                if (!concentration || damage>4){
	                    Gamestate.add_message("Agh! Action disrupted...",image,Color.WHITE);
	                    action_in_progress = null;
	                }
	            }
	            if (damage>2 && has_status_effect(Status_effect_code.EF_SLEEP)){
	                status_effects.get(Status_effect_code.EF_SLEEP).level = Math.max(0, status_effects.get(Status_effect_code.EF_SLEEP).level - (damage / 2));
	                if (status_effects.get(Status_effect_code.EF_SLEEP).level<=0)
	                    status_effects.get(Status_effect_code.EF_SLEEP).lasts_until = 0;
	            }
	            if (damage>1 && energy_for_getting_hit)
	                energy = Math.min(max_energy,energy+2);
	            if (damage>=10){
	                double stun_seconds = (damage/5.0) - (constitution) - Gamestate.w.randint(0,3);
	                boolean immune = false;
					for (Character_action a : actions)
						if (a.action_code==Character_action_code.CA_STUN_RESISTANCE)
							immune = true;
	                if (!immune && stun_seconds > 0){
	                    status_effects.put(Status_effect_code.EF_STUN,new Effect_parameters(stun_seconds));
	                    action_in_progress = null;
	                    current_stance = null;
	                }
	            }
	    }

	    void die(){
	            pos = null;
	            alive = false;
	            about_to_die = false;
	            Gamestate.add_sound("character_killed");
	            Gamestate.add_message("Knocked unconscious!",image,Color.WHITE);
	            Gamestate.p.after_character_leaves(false);
	            Gamestate.w.last_char_death = Gamestate.w.clock;
	    }

	    int flee(){
	            // return the number of characters that flee;
	            if (can_act() && !has_status_effect(Status_effect_code.EF_GRAPPLE) && !has_status_effect(Status_effect_code.EF_PETRIFY)){
	                int gold = Gamestate.w.collected_gold / Gamestate.p.n_surviving_chars();
	                slide_from = pos;
	                slide_to = Position_code.POS_DOWN;
	                pos = null;
	                alive = false;
	                fled = true;
	                slide_start = Gamestate.w.clock;
	                slide_finish = Gamestate.w.clock + 0.3;
	                Gamestate.w.collected_gold = Gamestate.w.collected_gold - gold;
	                Gamestate.w.removed_gold = Gamestate.w.removed_gold + gold;
	                if (xp > 0 || gold > 0){
	                	Gamestate.add_message("Fled with "+xp+" XP and "+gold+" gold",image,Color.WHITE);
	                } else {
	                	Gamestate.add_message("Fled!",image,Color.WHITE);
	                }
                    Gamestate.add_sound("whoosh2");
	                Gamestate.p.after_character_leaves(true);
	                return 1;
	            } else {
	                return 0;
	            }
	    }


	    boolean can_act(){
	    	return (alive && !about_to_die && action_in_progress==null && !has_status_effect(Status_effect_code.EF_PARALYSIS) && !has_status_effect(Status_effect_code.EF_PETRIFY) && !has_status_effect(Status_effect_code.EF_SLEEP) && !has_status_effect(Status_effect_code.EF_STUN) && !(drowning && drowning_from < Gamestate.w.clock - 60));
	    }

	    boolean has_status_effect(Status_effect_code ef_code){
	            if (status_effects.containsKey(ef_code))
	                if (status_effects.get(ef_code).lasts_until >= Gamestate.w.clock)
	                    return true;
	            return false;
	    };

	    int get_reflexes(){
	    	int bonus = 0;
            if (has_status_effect(Status_effect_code.EF_REFLEXES))
                bonus = bonus + 2;
	    	return reflexes + bonus;
	    }

	    int current_damage(){
	            int bonus = 0;
	            if (has_status_effect(Status_effect_code.EF_BATTLECRY))
	                bonus = bonus + 2;
	            if (has_status_effect(Status_effect_code.EF_RECKLESS))
	                bonus = bonus + 2;
	            if (has_status_effect(Status_effect_code.EF_FRONTROWBUFF))
	                bonus = bonus + 3;
	            if (current_stance==Stance_code.ST_BERSERK)
	                bonus = bonus + 3;
	            if (has_status_effect(Status_effect_code.EF_FORCEBRAND))
	                bonus = bonus + status_effects.get(Status_effect_code.EF_FORCEBRAND).level;
	            return damage + bonus;
	    }

	    int current_penetration(){
	            int bonus = 0;
	            return penetration + bonus;
	    }

	    int current_evasion(){
	            int bonus = 0;
	            if (has_status_effect(Status_effect_code.EF_BLESSING))
	            	bonus = bonus + 1;
	            if (has_status_effect(Status_effect_code.EF_RECKLESS))
	                bonus = bonus - 2;
	            if (has_status_effect(Status_effect_code.EF_DODGE))
	                bonus = bonus + 2;
	            if (has_status_effect(Status_effect_code.EF_EV))
	                bonus = bonus + status_effects.get(Status_effect_code.EF_EV).level;
	            if (has_status_effect(Status_effect_code.EF_RIGHTEOUSNESS))
	                bonus = bonus + 1;
	            if (current_stance == Stance_code.ST_ALL_OUT_DEFENSE)
	                bonus = bonus + 3;
	            if (has_status_effect(Status_effect_code.EF_PARALYSIS) || has_status_effect(Status_effect_code.EF_SLEEP))
	                bonus = bonus - 8;
	            if (has_status_effect(Status_effect_code.EF_GRAPPLE) || has_status_effect(Status_effect_code.EF_STUN))
	                bonus = bonus - 3;
	            return Math.max(-3,evasion + bonus);
	    }

	    int current_armor(){
	            int bonus = 0;
	            if (current_stance == Stance_code.ST_ALL_OUT_DEFENSE)
	                bonus = bonus + 3;
	            if (has_status_effect(Status_effect_code.EF_SHIELD))
	                bonus = bonus + status_effects.get(Status_effect_code.EF_SHIELD).level;
	            return armor + bonus;
	    }

	    int current_aggro(){
	            int bonus = 0;
	            if (has_status_effect(Status_effect_code.EF_RIGHTEOUSNESS))
	                bonus = bonus + 1;
	            if (has_status_effect(Status_effect_code.EF_BATTLECRY))
	                bonus = bonus + 2;
	            if (has_status_effect(Status_effect_code.EF_TAUNT))
	                bonus = bonus + 1;
	            if (has_status_effect(Status_effect_code.EF_TAUNT3))
	                bonus = bonus + 3;
	            if (has_status_effect(Status_effect_code.EF_INSULT))
	                bonus = bonus + 2;
	            if (has_status_effect(Status_effect_code.EF_CURSE))
	                bonus = bonus + 6;
	            return aggro + bonus;
	    }

	    double current_missile_period(){
	            int bonus = 0;
	            if (has_status_effect(Status_effect_code.EF_RAPIDITY))
	                bonus = bonus + 2;
	            return 5 * Math.pow(0.9,rapidity + bonus);
	    }

	    double current_melee_period(){
	            int bonus = 0;
	            for (Character_action a : actions){
	                if (a.action_code==Character_action_code.CA_LEADER)
	                    if (pos==Position_code.POS_C_FRONT_2)
	                        bonus = bonus + 2;
	            }
	            if (has_status_effect(Status_effect_code.EF_SPEED))
	            	bonus = bonus + 2;
	            if (current_stance==Stance_code.ST_BERSERK)
	                bonus = bonus + 2;
	            return 5 * Math.pow(0.9,speed + bonus);
	    }

	    int current_aim(){
	            int bonus = 0;
	            if (has_status_effect(Status_effect_code.EF_BLESSING))
	            	bonus = bonus + 1;
	            return aim + bonus;
	    }

	    int current_accuracy(){
	            int bonus = 0;
	            if (has_status_effect(Status_effect_code.EF_ACCURATE))
	            	bonus = bonus + 2;
	            if (has_status_effect(Status_effect_code.EF_BLESSING))
	            	bonus = bonus + 1;
	            return accuracy + bonus;
	    }

	}

	public Party(){
		chars = new ArrayList<Character>();
	}

	ArrayList<Character> chars;
	int xp_multiplier;
	double take_cover_success_chance; // kludge

    void reset(List<Charsheet> charsheets){
        chars = new ArrayList<Character>();
        ArrayList<Position_code> posses_available = new ArrayList<Position_code>();
        posses_available.add(Position_code.POS_C_FRONT_2);
        posses_available.add(Position_code.POS_C_FRONT_1);
        posses_available.add(Position_code.POS_C_FRONT_3);
        posses_available.add(Position_code.POS_C_BACK_2);
        posses_available.add(Position_code.POS_C_BACK_1);
        posses_available.add(Position_code.POS_C_BACK_3);
        for(int i=0; i<charsheets.size(); i++){
        	Position_code this_pos=null, wanted=Campaignstate.char_name_to_favorite_position.get(charsheets.get(i).name);
        	if (wanted!=null && posses_available.contains(wanted)){
        		this_pos = wanted;
        	} else {
        		this_pos = posses_available.get(0);
        	}
    		posses_available.remove(this_pos);
        	Character c = new Character(charsheets.get(i),i,this_pos);
            c.slide_from = Position_code.POS_DOWN;
            c.slide_to = c.pos;
            c.slide_start = Gamestate.w.clock;
            c.slide_finish = Gamestate.w.clock + 0.3;
            chars.add(c);
		}
        if (chars.size()==6)
            xp_multiplier = 100;
        else if (chars.size()==5)
            xp_multiplier = 130;
        else if (chars.size()==4)
            xp_multiplier = 160;
        else
            xp_multiplier = 200;
        take_cover_success_chance = 0;
	}

    Character get_char_at_pos(Position_code pos){
    	for (Character c : chars){
    		if (c.pos == pos)
    			return c;
    	}
    	return null;
	}

    void after_character_leaves(boolean fled){
		int count = 0;
		for (Character c : chars){
			if (c.pos!=null && c.alive && !c.has_status_effect(Status_effect_code.EF_PETRIFY))
				count = count + 1;
		}
		if (count==0){
			int n_fled = 0;
			int n_dead = 0;
			for (Character c : chars){
				if (c.fled){
					n_fled = n_fled + 1;
				} else {
					n_dead = n_dead + 1;
				}
			}
			if (fled){
		    	Gamestate.add_message("All characters have fled the scene...",null,Color.WHITE);
			} else {
				if (n_dead==1)
					Gamestate.add_message("Alas, " + n_dead + " character has been incapacitated...","skeleton",Color.WHITE);
				else
					Gamestate.add_message("Alas, " + n_dead + " characters have been incapacitated...","skeleton",Color.WHITE);
				if (n_fled > 0)
					Gamestate.add_message("But " + n_fled + " survived to fight another day!",null,Color.WHITE);
			}
			Gamestate.quit_after_pause = true;
		} else {
			if (Gamestate.p.n_surviving_front_chars()==0)
				Gamestate.p.push_char_forward();
		}
	}

    int n_surviving_front_chars(){
        int count = 0;
        for (Character c : chars){
            if (c.pos!=null && c.front_row() && c.alive && !(c.about_to_die))
                count = count + 1;
        }
        return count;
	}

    int n_surviving_back_chars(){
        int count = 0;
        for (Character c : chars)
            if (c.pos!=null && !c.front_row() && c.alive && !c.about_to_die)
                count = count + 1;
        return count;
	}

    boolean push_char_forward(){
        Position_code push_to = last_free_front_pos();
        if (push_to==null)
            return false;
        for (Position_code pos : Arrays.asList(Position_code.POS_C_BACK_2,Position_code.POS_C_BACK_1,Position_code.POS_C_BACK_3)){
            Character c = get_char_at_pos(pos);
            if (c!=null){
                c.slide_from = c.pos;
                c.pos = push_to;
                c.slide_to = push_to;
                c.slide_start = Gamestate.w.clock;
                c.slide_finish = Gamestate.w.clock + 0.1;
                return true;
            }
        }
        return false;
	}

    Position_code last_free_front_pos(){
        if (get_char_at_pos(Position_code.POS_C_FRONT_2)==null)
            return Position_code.POS_C_FRONT_2;
        if (get_char_at_pos(Position_code.POS_C_FRONT_1)==null)
            return Position_code.POS_C_FRONT_1;
        if (get_char_at_pos(Position_code.POS_C_FRONT_3)==null)
            return Position_code.POS_C_FRONT_3;
        return null;
	}

    Position_code last_free_back_pos(){
        if (get_char_at_pos(Position_code.POS_C_BACK_2)==null)
            return Position_code.POS_C_BACK_2;
        if (get_char_at_pos(Position_code.POS_C_BACK_1)==null)
            return Position_code.POS_C_BACK_1;
        if (get_char_at_pos(Position_code.POS_C_BACK_3)==null)
            return Position_code.POS_C_BACK_3;
        return null;
	}

    boolean move_character(Character c){
        if (n_surviving_chars()==1)
            return false;
        Position_code push_to;
        if (c.alive && !c.about_to_die && c.pos!=null){
            if (c.front_row()){
                if (n_surviving_front_chars()==1)
                    return false;
                push_to = last_free_back_pos();
                if (push_to==null)
                    return false;
            } else {
            	push_to = last_free_front_pos();
                if (push_to==null)
                    return false;
            }
            c.slide_from = c.pos;
            c.pos = push_to;
            c.slide_to = push_to;
            c.slide_start = Gamestate.w.clock;
            c.slide_finish = Gamestate.w.clock + 0.1;
            return true;
        } else {
            return false;
        }
	}

    boolean move_character_to(Character c, Position_code pos){
    	if (!c.alive || c.about_to_die)
    		return false;
        if (c.front_row())
            if (get_char_at_pos(pos)==null && n_surviving_front_chars()==1 && (pos==Position_code.POS_C_BACK_1 || pos==Position_code.POS_C_BACK_2 || pos==Position_code.POS_C_BACK_3))
                return false;
        if (c.alive && !c.about_to_die && c.pos!=null && c.pos!=pos && !c.has_status_effect(Status_effect_code.EF_GRAPPLE) && !c.has_status_effect(Status_effect_code.EF_PETRIFY)){
            Character displaced = get_char_at_pos(pos);
            if (displaced!=null){
                if (!displaced.can_act() || displaced.has_status_effect(Status_effect_code.EF_GRAPPLE) || displaced.has_status_effect(Status_effect_code.EF_PETRIFY)){
                    return false;
                } else {
                    displaced.slide_from = displaced.pos;
                    displaced.pos = c.pos;
                    displaced.slide_to = c.pos;
                    displaced.slide_start = Gamestate.w.clock;
                    displaced.slide_finish = Gamestate.w.clock + 0.1;
                    c.slide_from = c.pos;
                    c.pos = pos;
                    c.slide_to = pos;
                    c.slide_start = Gamestate.w.clock;
                    c.slide_finish = Gamestate.w.clock + 0.1;
                }
            } else {
                c.slide_from = c.pos;
                c.pos = pos;
                c.slide_to = pos;
                c.slide_start = Gamestate.w.clock;
                c.slide_finish = Gamestate.w.clock + 0.1;
            }
            return true;
        } else {
            return false;
        }
	}

    Character random_target_from(List<Position_code> posses, Mob hated_by){
        ArrayList<Character> options = new ArrayList<Character>();
        ArrayList<Double> preferences = new ArrayList<Double>();
        ArrayList<Double> cumulative_preferences = new ArrayList<Double>();
        double this_preference, so_far, sum, u;
        for (Position_code pos : posses){
            Character t = get_char_at_pos(pos);
            if (t!=null && !t.has_status_effect(Status_effect_code.EF_PETRIFY)){
                options.add(t);
                if (hated_by!=null && t.pos.opposite(hated_by.pos)){
                    this_preference = 0.1;
                } else {
                    this_preference = 1.0;
                }
                if (hated_by!=null && hated_by.focused_on==t)
                	this_preference = this_preference * 8;
                this_preference = this_preference * Math.pow(1.6,t.current_aggro());
                preferences.add(this_preference);
            }
        }
        if (options.size()==0)
            return null;
        sum = 0;
        for (double preference : preferences){
        	sum = sum + preference;
        }
        so_far = 0;
        for (double preference : preferences){
            cumulative_preferences.add(so_far + preference / sum);
            so_far = so_far + preference / sum;
        }
        u = Gamestate.w.rand.nextDouble();
        for (int i=0; i<preferences.size(); i++){
            if (cumulative_preferences.get(i) >= u)
                return options.get(i);
        }
        return null;
	}

    Character random_target(Mob hated_by){
        return random_target_from(Arrays.asList(Position_code.POS_C_FRONT_1,Position_code.POS_C_FRONT_2,Position_code.POS_C_FRONT_3,Position_code.POS_C_BACK_1,Position_code.POS_C_BACK_2,Position_code.POS_C_BACK_3),hated_by);
	}

    Character random_front_target(Mob hated_by){
        return random_target_from(Arrays.asList(Position_code.POS_C_FRONT_1,Position_code.POS_C_FRONT_2,Position_code.POS_C_FRONT_3),hated_by);
	}

    Character random_back_target(Mob hated_by){
        return random_target_from(Arrays.asList(Position_code.POS_C_BACK_1,Position_code.POS_C_BACK_2,Position_code.POS_C_BACK_3),hated_by);
	}

    int n_surviving_chars(){
        int count = 0;
        for (Character c : chars)
            if (c.pos!=null && c.alive && !c.about_to_die)
                count = count + 1;
        return count;
	}

    boolean fear_resistant(){
    	for (Character c : chars)
    		if (c.alive && !c.about_to_die)
    			for (Character_action a : c.actions)
    				if (a.action_code==Character_action_code.CA_PADRE)
    					return true;
    	return false;
    }

}



